#include "mesh.h"
#include <cassert>
#include <cstring>
#include "scene/mesh_loader.h"

namespace vkrender
{
    std::vector<VkVertexInputBindingDescription> MeshVertex::getBindingDescriptions()
    {
        // number of vertex buffers.
        std::vector<VkVertexInputBindingDescription> bindingDescriptions(1);
        bindingDescriptions[0].binding = 0;
        bindingDescriptions[0].stride = sizeof(MeshVertex);
        bindingDescriptions[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

        return bindingDescriptions;
    }

    std::vector<VkVertexInputAttributeDescription> MeshVertex::getAttributeDescriptions()
    {
        // vertex attributes
        std::vector<VkVertexInputAttributeDescription> attributeDescriptions(3);
        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[0].offset = offsetof(MeshVertex, position);

        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(MeshVertex, color);

        attributeDescriptions[2].binding = 0;
        attributeDescriptions[2].location = 2;
        attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[2].offset = offsetof(MeshVertex, texcoords);

        return attributeDescriptions;
    }

    VulkanModel::VulkanModel(const std::shared_ptr<VulkanDeviceContext>& context, const std::string& filepath)
    {
        this->context = context;
        MeshLoader loader;
        loader.load_from_file(filepath);
        createVertexBuffers(loader.vertices, loader.indices);
    }

    VulkanModel::~VulkanModel() 
    {
        vkFreeMemory(context->device, vertexBufferMemory, nullptr);
        vkFreeMemory(context->device, indexBufferMemory, nullptr);
        vkDestroyBuffer(context->device, vertexBuffer, nullptr);
        vkDestroyBuffer(context->device, indexBuffer, nullptr);
    }

    void VulkanModel::createVertexBuffers(
        const std::vector<MeshVertex>& vertices,
        const std::vector<uint32_t>& indices)
    {
        // TODO: use staging buffer

        vertexCount = static_cast<uint32_t>(vertices.size());
        VkDeviceSize bufferSize = sizeof(vertices[0]) * vertexCount;

        this->context->createBuffer(
            bufferSize,
            VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            vertexBuffer, vertexBufferMemory);

        void* data;
        vkMapMemory(context->device, vertexBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, vertices.data(), static_cast<size_t>(bufferSize));
        vkUnmapMemory(context->device, vertexBufferMemory);

        indexCount = static_cast<uint32_t>(indices.size());
        VkDeviceSize indexbuffersize = sizeof(uint32_t) * indexCount;

        this->context->createBuffer(
            indexbuffersize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            indexBuffer, indexBufferMemory);

        vkMapMemory(context->device, indexBufferMemory, 0, indexbuffersize, 0, &data);
        memcpy(data, indices.data(), static_cast<size_t>(indexbuffersize));
        vkUnmapMemory(context->device, indexBufferMemory);
    }

    void VulkanModel::draw(VkCommandBuffer commandBuffer) const
    {
        vkCmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0);
    }

    void VulkanModel::bind(VkCommandBuffer commandBuffer) const
    {
        VkBuffer buffers[] = { vertexBuffer };
        VkDeviceSize offsets[] = { 0 };
        vkCmdBindVertexBuffers(commandBuffer, 0, 1, buffers, offsets);
        vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VkIndexType::VK_INDEX_TYPE_UINT32);
    }
}
